package com.zookeeper.case2;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * @author WangGuoLiang
 * @program ZooKeeper
 * @description 分布式锁案例
 * @create 2021-09-05 10:36
 **/
public class DistributeLock {
    ZooKeeper zooKeeper;
    private CountDownLatch countDownLatch = new CountDownLatch(1);
    private CountDownLatch withLatch = new CountDownLatch(1);
    private String qname;
    private String s;

    public DistributeLock() throws IOException, InterruptedException, KeeperException {
        // 连接zookeeper
         zooKeeper = new ZooKeeper("47.106.199.16:2181", 2000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                //监听前一个节点

                //如果连接上zookeeper countDownLatch释放
                if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                    //当前状态为连接上则释放countDownLatch
                    countDownLatch.countDown();
                }
                //withLatch 释放
                if (watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(qname)){
//                    当前类型为 删除节点 并且是删除前一个监听的节点时则释放
                    withLatch.countDown();
                }

            }
        });
         //等待zookeeper真正的连接上后再执行下面代码
         countDownLatch.await();

        //判断根节点/locks是否存在
        Stat exists = zooKeeper.exists("/locks", false);
        if (exists == null){
            //不存在根节点 则创建
            zooKeeper.create("/locks", "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

        }
    }

    /**
     * @方法描述  对zk加锁
     * @param:
     * @return  void
     * @author  WGL
     * @date  2021/9/5 10:40
     */
    public void zkLock(){
        //创建对应的临时带序号节点
        try {
            s = zooKeeper.create("/locks/seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

            //判断当前节点是否最小序号节点 是则获得锁  不是则监听前一个锁
            //获得这个节点下的所有节点名称
            List<String> children = zooKeeper.getChildren("/locks", false);
            //如果只有一个节点 那么就可以直接获得锁  如果有多个 就要判断最小的那个 然后使得该节点监听其前一个节点
            if (children.size() == 1){
                return;
            } else {
                //排序
                Collections.sort(children);

                //获得节点的名称 seq-000000
                String substring = s.substring("/locks/".length());
                //找出该节点在集合中的位置
                int i = children.indexOf(substring);

                if (i == -1){
                    System.out.println("数据异常");
                } else if (i == 0){
                    //第一个节点 直接获得锁
                    return;
                } else {
                    //需要监听前一个节点
                    //获取前一个节点的路径
                    qname = "/locks/"+children.get(i-1);
                    //获取路径并开启监听
                    byte[] data = zooKeeper.getData(qname, true, null);
                    //等待上面的监听完成
                    withLatch.await();

                    return;
                }
            }

        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }

    /**
     * @方法描述  解锁
     * @param:
     * @return  void
     * @author  WGL
     * @date  2021/9/5 10:40
     */
    public void unZkLock(){
        //删除节点
        try {
            zooKeeper.delete(s, -1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }
}

